﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RoardersAgentSim.Agent;

namespace RoardersAgentSim.Map
{
    public enum NodeType
    {
        NORMAL_NODE,
        SHELTER_NODE,
        HOSPITAL_NODE
    }
    /// <summary>
    /// Node class definition
    /// </summary>
    public class Node
    {
        #region private fields
        #endregion

        #region node properties
        /// <summary>
        /// The node ID (for identification purposes)
        /// </summary>
        public string NodeID { get; private set; }
        /// summary>
        /// Type of the node
        /// </summary>
        public NodeType NodeType { get; private set; }
        /// <summary>
        /// The zone of the node
        /// </summary>
        public Zone Zone { get; set; }
        #region ingoing and outgoing links
        /// <summary>
        /// Sets of in-going links by walking
        /// </summary>
        public List<RoadLink> A_plus_Walk { get; private set; }
        /// <summary>
        /// Sets of outgoing links by walking
        /// </summary>
        public List<RoadLink> A_minus_Walk { get; private set; }
        /// <summary>
        /// Sets of in-going links by car
        /// </summary>
        public List<RoadLink> A_plus_Car { get; private set; }
        /// <summary>
        /// Sets of outgoing links by car
        /// </summary>
        public List<RoadLink> A_minus_Car { get; private set; }

        /// <summary>
        /// Shelter size if node is shelter
        /// </summary>
        public int ShelterCapacity { get; private set; }
        #endregion

        #region public dynamic properties
        /// <summary>
        /// Determine if the node has shelter
        /// </summary>
        public bool HasShelter
        {
            get
            {
                switch (this.NodeType)
                {
                    case Map.NodeType.HOSPITAL_NODE:
                    case Map.NodeType.SHELTER_NODE:
                        return true;
                    default:
                        break;
                }
                return false;
            }
        }
        #endregion

        #region export attributes

        public int DepartureTime
        {
            get;
            set;
        }

        #endregion


        #region visual helpers
        public Coords Coords { get; private set; }
        #endregion

        #endregion

        #region constructor
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="id"></param>
        /// <param name="type"></param>
        public Node(string id, NodeType type, Coords coords = null)
        {
            NodeID = id;
            NodeType = type;
            Coords = null;
            Init();
        }
        /// <summary>
        /// node initialiser
        /// </summary>
        private void Init()
        {
            A_minus_Car = new List<RoadLink>();
            A_minus_Walk = new List<RoadLink>();
            A_plus_Walk = new List<RoadLink>();
            A_plus_Car = new List<RoadLink>();
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="nodeMapping"></param>
        /// <param name="zone"></param>
        public Node(CSVUtils.CSVMapping.NodeMapping nodeMapping, Zone zone)
        {
            this.NodeID = nodeMapping.NodeID;
            switch (nodeMapping.NodeType.ToUpper().Trim())
            {
                case "NORMAL":
                    this.NodeType = Map.NodeType.NORMAL_NODE;
                    this.ShelterCapacity = -1;
                    break;
                case "HOSPITAL":
                    this.NodeType = Map.NodeType.HOSPITAL_NODE;
                    this.ShelterCapacity = nodeMapping.ShelterCapacity;
                    break;
                case "SHELTER":
                    this.NodeType = Map.NodeType.SHELTER_NODE;
                    this.ShelterCapacity = nodeMapping.ShelterCapacity;
                    break;
                default:
                    throw new Exception("Unrecognized node type");
            }
            this.Coords = new Coords(nodeMapping.XCoord, nodeMapping.YCoord);
            this.Zone = zone;
            Init();
        }
        #endregion

        #region public methods

        /// <summary>
        /// check if 2 nodes are neighbors(if from the first node we can get to the second one)
        /// </summary>
        /// <param name="secondNode"></param>
        /// <returns></returns>
        public bool IsDirectionalNeighborWith(Node secondNode)
        {
            if (this.A_minus_Car.Any(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0))
                return true;
            if (this.A_minus_Walk.Any(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0))
                return true;
            return false;
        }


        /// <summary>
        /// check if 2 nodes are neighbors(if from the first node we can get to the second one)
        /// </summary>
        /// <param name="secondNode"></param>
        /// <returns></returns>
        public bool IsDirectionalNeighborWith(Node secondNode, LinkType linkType)
        {
            switch (linkType)
            {
                case LinkType.CAR_LINK:
                    if (this.A_minus_Car.Any(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0))
                        return true;
                    break;
                case LinkType.WALK_LINK:
                    if (this.A_minus_Walk.Any(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0))
                        return true;
                    break;
            }

            return false;
        }

        public RoadLink GetConnectingLink(Node secondNode, LinkType linkType)
        {
            switch (linkType)
            {
                case LinkType.CAR_LINK:
                    RoadLink rl = this.A_minus_Car.FirstOrDefault(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0);
                    if (rl != null)
                        return rl;
                    break;
                case LinkType.WALK_LINK:
                    rl = this.A_minus_Walk.FirstOrDefault(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0);
                    if (rl != null)
                        return rl;
                    break;
            }
            return null;
        }

        public RoadLink GetConnectingLink(Node secondNode)
        {
            RoadLink rl = this.A_minus_Car.FirstOrDefault(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0);
            if (rl != null)
                return rl;
            rl = this.A_minus_Walk.FirstOrDefault(x => string.Compare(x.Link.Destination.NodeID, secondNode.NodeID) == 0);
            if (rl != null)
                return rl;
            return null;
        }

        /// <summary>
        /// check if 2 nodes are neighbors (the direction is not important)
        /// </summary>
        /// <param name="secondNode"></param>
        /// <returns></returns>
        public bool IsNeighborWith(Node secondNode)
        {
            if (!this.IsDirectionalNeighborWith(secondNode))
            {
                if (this.A_plus_Car.Any(x => string.Compare(x.Link.Source.NodeID, secondNode.NodeID) == 0))
                    return true;
                if (this.A_plus_Walk.Any(x => string.Compare(x.Link.Source.NodeID, secondNode.NodeID) == 0))
                    return true;
                return false;
            }
            return true;
        }

        /// <summary>
        /// Add in going link
        /// </summary>
        /// <param name="link"></param>
        public void AddInGoingWalkLink(RoadLink link)
        {
            link.Link.Destination = this;
            A_plus_Walk.Add(link);
        }
        /// <summary>
        /// add the out going link
        /// </summary>
        /// <param name="link"></param>
        public void AddOutGoingWalkLink(RoadLink link)
        {
            link.Link.Source = this;
            A_minus_Walk.Add(link);
        }

        /// <summary>
        /// Add in going link
        /// </summary>
        /// <param name="link"></param>
        public void AddInGoingCarLink(RoadLink link)
        {
            link.Link.Destination = this;
            A_plus_Car.Add(link);
        }
        /// <summary>
        /// add the out going link
        /// </summary>
        /// <param name="link"></param>
        public void AddOutGoingCarLink(RoadLink link)
        {
            link.Link.Source = this;
            A_minus_Car.Add(link);
        }

        #endregion
    }
}
